package com.haoqizhe.kernel.shiro.jwt;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.haoqizhe.kernel.commons.constants.HeaderConstants;
import com.haoqizhe.kernel.commons.enums.ResultCode;
import com.haoqizhe.kernel.commons.exceptions.BusinessException;
import com.haoqizhe.kernel.jwt.utils.JwtTokenUtil;
import com.haoqizhe.kernel.redis.common.util.RedisUtil;
import com.haoqizhe.kernel.shiro.properties.ShiroProperties;
import com.haoqizhe.kernel.web.result.DefaultErrorResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * jwt shiro过滤器
 *
 * @author haoqizhe.li
 * @date 2021/01/25 19:44
 */
@Slf4j
public class JwtFilter extends AuthenticatingFilter {

    private JwtTokenUtil jwtTokenUtil;

    private RedisUtil redisUtil;

    private ShiroProperties shiroProperties;


    public JwtFilter(JwtTokenUtil jwtTokenUtil, RedisUtil redisUtil, ShiroProperties shiroProperties) {
        this.jwtTokenUtil = jwtTokenUtil;
        this.redisUtil = redisUtil;
        this.shiroProperties = shiroProperties;
    }


    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = WebUtils.toHttp(request);
        String token = req.getHeader(HeaderConstants.X_TOKEN);
        if (StrUtil.isBlank(token)) {
            throw new AuthenticationException("token is empty");
        }
        if (jwtTokenUtil.isExpiration(token)) {
            throw new AuthenticationException("token expired");
        }

        if (shiroProperties.getSingleLogin()) {
            JwtRedis jwtRedis = new JwtRedis(redisUtil, shiroProperties, jwtTokenUtil);
            if (!jwtRedis.existToken(token)) {
                throw new AuthenticationException("token invalid");
            }
        }

        return JwtToken.build(token, jwtTokenUtil);
    }

    /**
     * 没有权限处理
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse res = WebUtils.toHttp(response);

        res.setCharacterEncoding("UTF-8");
        res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        res.setContentType(MediaType.APPLICATION_JSON_VALUE);
        res.getWriter().write(JSONUtil.toJsonPrettyStr(DefaultErrorResult.failure(ResultCode.UNAUTHORIZED,
                new BusinessException(ResultCode.UNAUTHORIZED), HttpStatus.UNAUTHORIZED)));
        return false;
    }

    /**
     * 判断是否允许访问
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (this.isLoginRequest(request, response)) {
            return true;
        }
        boolean allowed = false;
        try {
            allowed = executeLogin(request, response);
        } catch (IllegalStateException e) {
            log.error("token 不能为空", e);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return allowed || super.isPermissive(mappedValue);
    }

    /**
     * 登录成功认证处理
     *
     * @param authenticationToken
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken authenticationToken, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        String url = WebUtils.toHttp(request).getRequestURI();
        log.debug("鉴权成功,token:{},url:{}", authenticationToken, url);

        JwtToken jwtToken = (JwtToken) authenticationToken;
        if (jwtToken == null) {
            return true;
        }

        String token = jwtToken.getToken();
        if (StrUtil.isBlank(token)) {
            return true;
        }

        if (!jwtTokenUtil.isRefreshToken(token)) {
            return true;
        }

        HttpServletResponse res = WebUtils.toHttp(response);


        String newToken = jwtTokenUtil.refreshToken(token);

        // 单点登陆
        if (shiroProperties.getSingleLogin()) {
            JwtRedis jwtRedis = new JwtRedis(redisUtil, shiroProperties, jwtTokenUtil);
            if (!jwtRedis.existToken(token)) {
                res.setStatus(ResultCode.PERMISSION_NO_TOKEN.code());
                return true;
            }

            jwtRedis.setToken(token, jwtToken.getUsername());
            jwtRedis.setUser(jwtToken.getUsername(), newToken);
        }

        // 刷新新的token
        res.setStatus(ResultCode.PERMISSION_REFRESH.code());
        res.setHeader(HeaderConstants.X_TOKEN, newToken);

        return true;
    }

    /**
     * 登录失败处理
     *
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        log.error("登录失败，token:" + token + ",error:" + e.getMessage(), e);
        return false;
    }
}
